import {
  BtHandler,
  Handler,
  NValueHandler,
  ZeroValueHandler,
} from "./handlers/index.ts";
import { OpHandler } from "./handlers/index.ts";
import { isFieldParam, isTupleParam, urlEncodeValue } from "./helper.ts";

type Operator =
  | "eq"
  | "ne"
  | "gt"
  | "ge"
  | "lt"
  | "le"
  | "bt"
  | "nb" // not between
  | "ct"
  | "sw" // start with
  | "ew" // end with
  | "ol" // or like
  | "nk" // not like
  | "il" // in list
  | "ni" // not in list
  | "nl" // is null
  | "nn" // not null
  | "ey" // empty
  | "ny" // not empty
  | "at" // always true
  | "af" // always false
;

export type Value = string | number;

export interface FieldParam<T, Extra> {
  label: Extra extends undefined ? keyof T : keyof T | Extra;
  op: Operator;
  value?: Value | Value[];
}

export type FieldTupleParam<T, Extra> =
  | [Extra extends undefined ? keyof T : keyof T | Extra, Operator]
  | [
    Extra extends undefined ? keyof T : keyof T | Extra,
    Operator,
    Value | Value[],
  ];

export interface OrderByParam<T, Extra = undefined> {
  label: Extra extends undefined ? keyof T : keyof T | Extra;
  order: "asc" | "desc";
}

export interface Param<T, Extra = undefined> {
  fields?:
    | FieldParam<T, Extra>[]
    | FieldTupleParam<T, Extra>[]
    | Record<string, FieldParam<T, Extra>[]>;
  orderBy?: OrderByParam<T, Extra>[];
  page?: number;
  size?: number;
  gexpr?: string;
}

const OP_HANDLER_MAP: Record<Operator, Handler> = {
  eq: new OpHandler(),
  ne: new OpHandler(),
  gt: new OpHandler(),
  ge: new OpHandler(),
  lt: new OpHandler(),
  le: new OpHandler(),
  bt: new BtHandler(),
  nb: new BtHandler(),
  ct: new OpHandler(),
  sw: new OpHandler(),
  ew: new OpHandler(),
  ol: new NValueHandler(),
  nk: new OpHandler(),
  il: new NValueHandler(),
  ni: new NValueHandler(),
  nl: new ZeroValueHandler(),
  nn: new ZeroValueHandler(),
  ey: new ZeroValueHandler(),
  ny: new ZeroValueHandler(),
  at: new ZeroValueHandler(),
  af: new ZeroValueHandler(),
};

export function buildQueryObj<T extends object, Extra = undefined>(
  param: Param<T, Extra>,
): Record<string, string> {
  checkParam(param);

  const obj = {};

  if (param.fields instanceof Array) {
    // 处理 fields
    param.fields.forEach((field) => {
      if (isFieldParam(field)) {
        OP_HANDLER_MAP[field.op].processObj(obj, field);
      } else if (isTupleParam(field)) {
        OP_HANDLER_MAP[field[1]].processObjTuple(obj, field);
      }
    });
  } else if (param.fields) {
    Object.keys(
      param.fields as Record<string, FieldParam<T, Extra>[]>,
    )
      .forEach((key) => {
        const fields = (
          param.fields as Record<string, FieldParam<T, Extra>[]>
        )?.[key];
        fields
          ?.forEach((field) => {
            return OP_HANDLER_MAP[field.op].processObj(obj, field, key);
          });
      });
  }

  if (param.orderBy) {
    Object.assign(obj, {
      orderBy: param.orderBy.map((order) => `${order.label}:${order.order}`).join(","),
    });
  }

  if (param.gexpr) {
    Object.assign(obj, {
      gexpr: encodeURIComponent(param.gexpr),
    });
  }

  if (param.page) {
    Object.assign(obj, {
      page: String(param.page),
    });
  }
  if (param.size) {
    Object.assign(obj, {
      size: String(param.size),
    });
  }

  return obj;
}

export function buildQuery<T extends object, Extra = undefined>(
  param: Param<T, Extra>,
): string {
  checkParam(param);

  let fieldStr = "";

  if (param.fields instanceof Array) {
    // 处理 fields
    fieldStr = param.fields
      ?.map((field) => {
        if (isFieldParam(field)) {
          return OP_HANDLER_MAP[field.op].process(field);
        } else if (field instanceof Array) {
          return OP_HANDLER_MAP[field[1]].processTuple(field);
        }
      })
      .filter((item) => {
        return item !== "";
      })
      .join("&");
  } else if (param.fields) {
    fieldStr = Object.keys(
      param.fields as Record<string, FieldParam<T, Extra>[]>,
    )
      .map((key) => {
        const fields = (
          param.fields as Record<string, FieldParam<T, Extra>[]>
        )?.[key];
        return fields
          ?.map((field) => {
            return OP_HANDLER_MAP[field.op].process(field, key);
          })
          .join("&");
      })
      .join("&");
  }

  const orderByStr = param.orderBy
    ? "orderBy=" +
      param.orderBy
        ?.map((order) => {
          return `${String(order.label)}:${order.order}`;
        })
        .join(",")
    : undefined;

  const gexprStr = param.gexpr
    ? `gexpr=${encodeURIComponent(param.gexpr)}`
    : undefined;

  const pageStr = param.page !== undefined ? `page=${param.page}` : undefined;

  const sizeStr = param.size !== undefined && param.size > 0
    ? `size=${param.size}`
    : undefined;

  // handle orderBy params
  return [pageStr, sizeStr, orderByStr, fieldStr, gexprStr]
    .filter((item) => item && item !== "")
    .join("&");
}

function checkParam<T, Extra>(param: Param<T, Extra>) {
  // check field params
  if (param.fields instanceof Array) {
    // 根据 op 来 check value 是否填写正确
    param.fields?.forEach((field) => {
      if (isFieldParam(field)) {
        OP_HANDLER_MAP[field.op].check(field);
      } else if (field instanceof Array) {
        OP_HANDLER_MAP[field[1]].checkTuple(field);
      }
    });
  } else if (param.fields) {
    // 分组的形式
    Object.keys(
      param.fields as Record<string, FieldParam<T, Extra>[]>,
    ).forEach((key) => {
      const fields = (param.fields as Record<string, FieldParam<T, Extra>[]>)?.[
        key
      ];
      fields?.forEach((field) => {
        OP_HANDLER_MAP[field.op].check(field);
      });
    });
  }

  if (
    typeof param.fields === "object" &&
    !Array.isArray(param.fields) &&
    param.gexpr === undefined
  ) {
    throw new Error("gexpr is required when fields is object");
  }
}
